/**
 * @file util.js 工具函数库
 * @description 
 * 通用式的工具函数
 * @version 0.1.0
 * @author lisfan
 * @copyright MIT
 * 
 * 
 * @markdown
 *
 * # 更新日志
 * 
 * ## 0.0.1
 * 
 * ### 2016/05/27
 *   - [bug fix] 修复了 XXX 问题
 *   - [feature] 增加了 YYY 功能
 *   - [improve] 优化了 ZZZ 代码
 *   
 * ## 0.1.0
 * ### 2016/06/08
 *   - [bug fix] 修复了 数据类型验证的错误 
 *   - [feature] 增加了 增加数个新工具函数，不一一列举
 *   - [improve] 优化了 健全各方法的逻辑
 *   
 * ### 2016/06/12
 *   - [bug fix] 修复了 extend无法对其他object类型递归合并的错误 
 */

(function (window) {
    var util = {
        /**
         * 数据类型验证：是否为undefind类型
         * @param   {*} value 需验证的值
         * @returns {Boolean} 是返回true，不是返回false
         */
        isUndefined: function (value) {
            return value === void 0;
        },
        /**
         * 数据类型验证：是否为null类型
         * @param   {*} value 需验证的值
         * @returns {Boolean} 是返回true，不是返回false
         */
        isNull: function (value) {
            return value === null;
        },

        /**
         * 数据类型验证：是否为undefind或null类型
         * @param   {*} value 需验证的值
         * @returns {Boolean} 是返回true，不是返回false
         */
        isUnknow: function (value) {
            return this.isUndefined(value) || this.isNull(value);
        },
        /**
         * 数据类型验证：是否为array类型
         * @param   {*} value 需验证的值
         * @returns {Boolean} 是返回true，不是返回false
         */
        isArray: function (value) {
            return !this.isUnknow(value) && value.constructor === Array;
        },
        /**
         * 数据类型验证：是否为string类型
         * @param   {*} value 需验证的值
         * @returns {Boolean} 是返回true，不是返回false
         */
        isString: function (value) {
            return !this.isUnknow(value) && value.constructor === String;
        },
        /**
         * 数据类型验证：是否为number类型
         * @param   {*} value 需验证的值
         * @returns {Boolean} 是返回true，不是返回false
         */
        isNumber: function (value) {
            return !this.isUnknow(value) && value.constructor === Number;
        },
        /**
         * 数据类型验证：是否为boolean类型
         * @param   {*} value 需验证的值
         * @returns {Boolean} 是返回true，不是返回false
         */
        isBoolean: function (value) {
            return !this.isUnknow(value) && value.constructor === Boolean;
        },
        /**
         * 数据类型验证：是否为function类型
         * @param   {*} value 需验证的值
         * @returns {Boolean} 是返回true，不是返回false
         */
        isFunction: function (value) {
            return !this.isUnknow(value) && value.constructor === Function;
        },
        /**
         * 数据类型验证：是否为date类型
         * @param   {*} value 需验证的值
         * @returns {Boolean} 是返回true，不是返回false
         */
        isDate: function (value) {
            return !this.isUnknow(value) && value.constructor === Date;
        },
        /**
         * 数据类型验证：是否为regexp类型
         * @param   {*} value 需验证的值
         * @returns {Boolean} 是返回true，不是返回false
         */
        isRegExp: function (value) {
            return !this.isUnknow(value) && value.constructor === RegExp;
        },
        /**
         * 数据类型验证：是否为error类型
         * @param   {*} value 需验证的值
         * @returns {Boolean} 是返回true，不是返回false
         */
        isError: function (value) {
            return !this.isUnknow(value) && value.constructor === Error;
        },
        /**
         * 数据类型验证：是否为原生object类型
         * @param   {*} value 需验证的值
         * @returns {Boolean} 是返回true，不是返回false
         */
        isPlainObject: function (value) {
            return !this.isUnknow(value) && value.constructor === Object;
        },
        /**
         * 数据类型验证：是否为object类型（特定类型如array等都会返回false）
         * @param   {*} value 需验证的值
         * @returns {Boolean} 是返回true，不是返回false
         */
        isObject: function (value) {
            if (this.isUnknow(value)) return false;
            else if (this.isArray(value)) return false;
            else if (this.isString(value)) return false;
            else if (this.isNumber(value)) return false;
            else if (this.isBoolean(value)) return false;
            else if (this.isFunction(value)) return false;
            else if (this.isDate(value)) return false;
            else if (this.isRegExp(value)) return false;
            else if (this.isError(value)) return false;
            else return true;
        },
        /**
         * 数据类型验证：是否为 dom 元素节点类型
         * @param   {*} value 需验证的值
         * @returns {Boolean} 是返回true，不是返回false
         */
        isElement: function (value) {
            if (this.isUndefined(window["HTMLElement"])) {
                //IE8以下浏览器
                return !this.isUnknow(value) && value.nodeType === 1;
            } else {
                return value instanceof HTMLElement;
            }
        },
        /**
         * 数据类型验证：是否为 dom 元素节点集合类型
         * @param   {*} value 需验证的值
         * @returns {Boolean} 是返回true，不是返回false
         */
        isCollection: function (value) {
            if (this.isUndefined(window["HTMLCollection"])) {
                //IE8以下浏览器
                return !this.isUnknow(value) && !this.isUnknow(value.length);
            } else {
                return value instanceof HTMLCollection;
            }
        },
        /**
         * 截断字符串首尾的空白符
         * @param   {String} str 源字符串
         * @returns {String} 返回去除后的新字符串
         */
        trim: function (str) {
            return this.isString(str) && str.toString().replace(/\s*(.*[^\s])\s*/g, '$1');
        },
        /**
         * 数据验证：是否为空字符串
         * @param   {String} value 需验证的值
         * @returns {Boolean} 是返回true，不是返回false
         */
        isEmpty: function (str) {
            return !this.isString(str) || this.trim(str).length === 0;
        },
        /**
         * 数据验证：是否为电话
         * @param   {Number||String} value 需验证的值
         * @returns {Boolean} 是返回true，不是返回false
         */
        isPhone: function (value) {
            return /^(0|86|17951)?(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$/.test(value);
        },
        /**
         * 数据验证：是否为正整数
         * @param   {*} value 需验证的值
         * @returns {Boolean} 是返回true，不是返回false
         */
        isInteger: function (value) {
            return this.isNumber(value) && value.toString().indexOf('.') < 0;
        },
        /**
         * 数据验证：是否为浮点数
         * @param   {*} value 需验证的值
         * @returns {Boolean} 是返回true，不是返回false
         */
        isFloat: function (value) {
            return this.isNumber(value) && value.toString().indexOf('.') >= 0;
        },

        /**
         * 复制或合并对象属性和方法
         * @param   {Object} oTarget 目标对象
         * @param   {Object} oSource 源对象
         * @param   {Boolean} deep      深层合并：为true时会将源对象中键的值为对象类型递归合并 
         * @returns {Object} 返回合并后的对象
         */
        extend: function (oTarget, oSource, deep) {
            //1.未定义oTarget时，设置为空对象
            //2.非对象格式时直接返回oTarget
            //valide
            if (this.isUndefined(oTarget)) {
                oTarget = {};
            } else if (!this.isObject(oTarget)) {
                return oTarget;
            }

            //valide
            //源对象oSource不是对象格时，直接返回oTarget
            if (!this.isPlainObject(oSource)) {
                return oTarget;
            }

            for (var key in oSource) {
                //若是原生对象且深度合并，则递归
                if (this.isPlainObject(oSource[key]) && !!deep) {
                    oTarget[key] = this.extend(oTarget[key], oSource[key], deep);
                } else {
                    oTarget[key] = oSource[key];
                }
            }

            return oTarget;
        },

        /**
         * 轻量模板解析器，替换内容使用“双花括号”包裹，双括号中间值为数据对象的键名，如（'{{title}}'）
         * @param   {String}   template 模板的HTML格式字符串
         * @param   {Object} oData  数据对象
         * @returns {String||Boolean} 返回替换数据后的HTML格式字符串，错误返回false
         */
        tpl: function (template, oData) {
            //valide
            if (!this.isString(template)) return false;

            //valide
            if (!this.isPlainObject(oData)) return false;

            var tpl = template.replace(/{{(.*?)}}/g, function (matched, $1) {
                return oData[$1];
            });

            return tpl;
        },


        /**
         * 遍历数组（回调函数的this指向原数组的当前元素）
         * @param {Array} arr      目标数组
         * @param {Function} callback 回调函数
         */
        each: function (arr, callback) {
            //valide
            if (!this.isArray(arr)) return false;

            //valide
            if (!this.isFunction(callback)) return false;

            var len = arr.length;
            for (var i = 0; i < len; i++) {
                callback.call(arr[i], arr[i], i, arr);
            }
        },
        /**
         * 过滤原数组，产生新数组，在回调函数中return true时表示保留，false表示不保留（回调函数的this指向原数组的当前元素）
         * @param {Array} arr      目标数组
         * @param {Function} callback 回调函数
         */
        grep: function (arr, callback) {
            //valide
            if (!this.isArray(arr)) return false;

            //valide
            if (!this.isFunction(callback)) return false;

            var newArr = [];
            var len = arr.length;
            for (var i = 0; i < len; i++) {
                var result = callback.call(arr[i], arr[i], i, arr);
                if (!!result) {
                    newArr.push(arr[i]);
                }
            }
        },
        /**
         * 遍历原数组，产生新数组，在回调函数中return的值会作为新数组的元素（回调函数的this指向原数组的当前元素）
         * @param {Array} arr      目标数组
         * @param {Function} callback 回调函数
         * @returns {Array} 返回新数组
         */
        map: function (arr, callback) {
            //valide
            if (!this.isArray(arr)) return false;

            //valide
            if (!this.isFunction(callback)) return false;

            var newArr = [];
            var len = arr.length;
            for (var i = 0; i < len; i++) {
                newArr.push(callback.call(arr[i], arr[i], i, arr));
            }
            return newArr;
        },
        /**
         * 创建 Element 节点对象，可以传入配置
         * @param   {String} htmlTag html元素标签名
         * @param   {Object} options 元素各属性配置，可以为空
         * @returns {Element} 返回创建成功的对象
         */
        createElement: function (htmlTag, options) {
            //valide
            if (!this.isString(htmlTag)) return false;

            var ele = document.createElement(htmlTag);
            this.extend(ele, options, true);
            return ele;
        },
        /**
         * 在 parentNode 节点对象末尾附加子节点对象
         * @param {Element} parentNode 目标对象
         * @param {Element} childNode  作为目标对象的子节点
         */
        appendChild: function (parentNode, childNode) {
            //valide
            if (!this.isElement(parentNode)) return false;

            //valide
            if (!this.isElement(childNode)) return false;

            if (!parentNode || !childNode) {
                return false;
            }
            parentNode.appendChild(childNode);
        },
        /**
         * 在 parentNode 节点对象起始处附加子节点对象
         * @param {Element} parentNode 目标对象
         * @param {Element} childNode  作为目标对象的子节点
         */
        prependChild: function (parentNode, childNode) {
            //valide
            if (!this.isElement(parentNode)) return false;

            //valide
            if (!this.isElement(childNode)) return false;

            if (!!parentNode.hasChildNodes) {
                parentNode.insertBefore(childNode, parentNode.firstChild);
            } else {
                this.appendChild(parentNode, childNode);
            }
        },
        /**
         * 在 targetNode 节点对象的前方附加兄弟节点
         * @param {Element} targetNode 目标对象
         * @param {Element} sourceNode  作为目标对象的兄弟节点，插入到目标节点的前面
         */
        insertBefore: function (targetNode, sourceNode) {
            //valide
            if (!this.isElement(targetNode)) return false;

            //valide
            if (!this.isElement(sourceNode)) return false;

            targetNode.parentNode.insertBefore(sourceNode, targetNode);
        },

        /**
         * 在 targetNode 节点对象的后方附加兄弟节点
         * @param {Element} targetNode 目标对象
         * @param {Element} sourceNode  作为目标对象的兄弟节点，插入到目标节点的后面
         */
        insertAfter: function (targetNode, sourceNode) {
            //valide
            if (!this.isElement(targetNode)) return false;

            //valide
            if (!this.isElement(sourceNode)) return false;

            if (!!targetNode.nextSibling) {
                targetNode.parentNode.insertBefore(sourceNode, targetNode.nextSibling);
            } else {
                this.appendChild(targetNode.parentNode, sourceNode);
            }
        },

        /**
         * 在 targetNode 节点对象上创建一个父节点，包裹它，并返回这个生成的父节点
         * @param {Element} parentNode 目标对象
         * @param {Element} childNode  作为目标对象的子节点
         */
        wrap: function (targetNode, sourceNode) {
            //valide
            if (!this.isElement(targetNode)) return false;

            //valide
            if (!this.isElement(sourceNode)) return false;

        },
        /**
         * 克隆节点
         * @param {Element} node 节点对象
         */
        clone: function (node) {
            //valide
            if (!this.isElement(node)) return false;

            return node.cloneNode(true);
        },
        /**
         * 移除所属 Element 节点对象的子节点对象
         * @param {Element} node 节点对象
         */
        remove: function (node) {
            //valide
            if (!this.isElement(node)) return false;

            node.parentNode.removeChild(node);
        },
        /**
         * 清空节点内容
         * @param   {Element}  node 节点对象
         * @returns {boolean} 清除失败时返回false，成功当前节点，以便链式调用
         */
        empty: function (node) {
            //valide
            if (!this.isElement(node)) return false;

            node.innerHTML = '';
            return node;
        },
        /**
         * 根据选择器类型获取节点对象（不获取tagName）
         * @param   {string||Element}     selector 选择器字符串
         * @param   {String||Element}     context  上下文环境节点对象，减少查找范围
         * @returns {Element||Collection}    返回节点对象或节点对象集合，未找到符合要求的节点时，返回带有长度length=0的对象
         */
        $: function (selector, context) {
            //valide
            if (!(this.isString(selector) || this.isElement(selector))) {
                return {
                    length: 0
                }
            };

            var parentNode;
            //存在参数context时
            if (this.isString(context) || this.isElement(context)) {
                parentNode = query(context, document);
                if (!parentNode) {
                    return {
                        length: 0
                    }
                }
            } else {
                parentNode = document;
            }


            return query(selector, parentNode);

            function query(selector, context) {
                if (util.isElement(selector)) {
                    //目标对象已经是dom 对象时，不再检测
                    return selector;
                }
                var aSelector = selector.split(' ');
                for (var i = 0; i < aSelector.length; i++) {
                    var firstLettet = util.trim(aSelector[i]).charAt(0);
                    if (firstLettet === '#') {
                        return context.getElementById(aSelector[i].slice(1));
                    } else if (firstLettet === '.') {
                        return context.getElementsByClassName(aSelector[i].slice(1));
                    }
                    /*else {
                        return context.getElementsByTagName(aSelector[i]);
                    }*/
                }
            }
        },
        /**
         * 在目标节点对象的上下文环境中查找符合selector的对象
         * @param   {string||Element}     selector 选择器字符串
         * @param   {String||Element}     context  上下文环境节点对象，减少查找范围
         * @returns {Element||Collection}    返回节点对象或节点对象集合，未找到符合要求的节点时，返回带有长度length=0的对象
         */
        find: function (context, selector) {
            return this.$(selector, context);
        },
        filter: function (nodeList, selector) {
            //TODO 
        },
        /**
         * 为 Element 节点对象增加样式
         * @param {Element}   ele       目标节点对象
         * @param {String} className 样式名
         */
        addClass: function (ele, className) {
            //valide
            if (!this.isElement(ele)) return false;

            //valide
            if (!this.isString(className)) return false;

            var newClassName = ' ' + ele.className + ' ';
            var aClassName = className.split(' ');
            this.each(aClassName, function (value) {
                //不存在，则增加
                var reg = new RegExp('\\s(' + value + ')\\s', 'g');
                //不存在，则增加
                if (newClassName.search(reg) < 0) {
                    newClassName += value + ' ';
                };
            });

            ele.className = this.trim(newClassName);
        },
        removeClass: function (ele, className) {
            //valide
            if (!this.isElement(ele)) return false;

            //valide
            if (!this.isString(className)) return false;


            var newClassName = ' ' + ele.className + ' ';
            var aClassName = className.split(' ');
            this.each(aClassName, function (value) {
                var reg = new RegExp('\\s(' + value + ')\\s', 'g');
                //存在，则移除
                newClassName = newClassName.replace(reg, '');
            });
            ele[i].className = this.trim(newClassName);
        },
        /**
         * 显示 Element 节点对象
         * @param {Element} ele 目标节点对象
         */
        show: function (ele) {
            //valide
            if (!this.isElement(ele)) return false;

            //已显示状态的值不理它
            if (ele.style.display === 'none') {
                ele.style.display = '';
            }
        },
        /**
         * 隐藏 Element 节点对象
         * @param {Element} ele 目标节点对象
         */
        hide: function (ele) {
            //valide
            if (!this.isElement(ele)) return false;

            ele.style.display = 'none';
        },
        /**
         * 切换 dom 对象显示状态，若 Element 节点对象为隐藏的，则显示，若 Element 节点对象是显示的，则隐藏
         * @param {Element} ele 目标节点对象
         */
        toggle: function (ele) {
            //valide
            if (!this.isElement(ele)) return false;

            if (ele.style.display === 'none') {
                this.show(ele);
            } else {
                this.hide(ele);
            }
        },
        /**
         * 检测滚动条是否已经滚动到目标元素ele的位置
         * @param   {Element}   [ele=document.body] 传入一个节点元素，如果不存在，则指向document.body，表示是否已经滚动至底部
         * @param   {number} [offsetSize=0] 当前元素位置的偏移尺寸：在目标元素的位置上“加减”该尺寸长度，使元素在离目前上方或下方offsetSize距离时就判断是是否已滚动至目标位置
         * @returns {Boolean}  若已滚动至目标位置，返回true，反之false
         */
        isScrollTo: function (ele, offsetSize) {
            //valide
            if (!this.isElement(ele)) {
                ele = document.body;
            }

            //valide
            if (!this.isNumber(offsetSize)) {
                ele = 0;
            }

            if (ele === document.body) {
                return document.body.scrollTop + window.innerHeight >= document.body.scrollHeight + offsetSize;
            } else {
                return document.body.scrollTop >= ele.offsetTop + offsetSize;
            }
        },
        /**
         * 滚动页面延迟触发回调
         * @param {Function} callback              回调函数
         * @param {Number} [delay=300]             延迟触发时间
         * @param {Boolean} [startTrigger=false]  初始化时，是否默认触发一次默认是否触发一次
         */
        scrollTrigger: function (callback, delay, startTrigger) {
            if (!this.isFunction(callback)) {
                return false;
            }

            if (!this.isNumber(delay)) {
                delay = 300;
            }

            if (!this.isBoolean(startTrigger)) {
                startTrigger = false;
            }

            /*
                        var timeouter = new Date().getTime();
            */
            var timeouter;
            //todo 用addEventListen 代替
            window.onscroll = function () {
                if (!util.isUndefined(timeouter)) {
                    clearTimeout(timeouter);
                }
                timeouter = setTimeout(function () {
                    callback();
                }, delay);
            }

            if (startTrigger) {
                callback();
            }
        },

        /**
         * 窗口尺寸改变延迟触发事件
         * @param {Function} callback              回调函数
         * @param {Number} [delay=300]             延迟触发时间
         * @param {Boolean} [startTrigger=false]  初始化时，是否默认触发一次默认是否触发一次
         */
        resizeTrigger: function (callback, delay, startTrigger) {
            if (!this.isFunction(callback)) {
                return false;
            }

            if (!this.isNumber(delay)) {
                delay = 300;
            }

            if (!this.isBoolean(startTrigger)) {
                startTrigger = false;
            }

            /*
                        var timeouter = new Date().getTime();
            */
            var timeouter;
            window.onresize = function () {
                if (!util.isUndefined(timeouter)) {
                    clearTimeout(timeouter);
                }
                timeouter = setTimeout(function () {
                    callback();
                }, delay);
            }

            if (startTrigger) {
                callback();
            }
        },

        /**
         * 检测目标对象是否在浏览器窗口的可视区域内（注意：目标对象有可能显示的偏移位置是根据其父元素而定的，确保你就是想要根据父元素的偏移位置）
         * @param {Object}   ele                    目标对象
         * @param {Boolean} [fullVisualMode=false] 为true时目标对象需要“完全”显示在窗口可视区域内，反之部分即可
         */
        isVisualArea: function (ele, fullVisualMode) {
            //valide
            if (!this.isElement(ele)) return false;

            fullVisualMode = !!fullVisualMode || false;

            //窗体尺寸+滚动距离 > 目标元素偏移距离 且 目标元素偏移距离 > 滚动距离（已滚出显示区域）
            if (!!fullVisualMode) {
                return window.innerHeight + document.body.scrollTop - ele.offsetHeight > ele.offsetTop && ele.offsetTop > document.body.scrollTop;
            } else {
                return window.innerHeight + document.body.scrollTop > ele.offsetTop && ele.offsetTop + ele.offsetHeight > document.body.scrollTop;
            }
        },
        /**
         * 分析url连接，取得参数对象
         * @param   {[[Type]]} [url=location.href] [[Description]]
         * @returns {[[Type]]} [[Description]]
         */
        getUrlParams: function (url) {
            if (!this.isString(url)) {
                url = location.href;
            }

            url = decodeURIComponent(url);

            var searchIndex = url.lastIndexOf('?');

            if (searchIndex < 0) {
                return null;
            }

            var search = url.slice(searchIndex + 1);
            var aParams = search.split('&');

            if (aParams.length === 0) {
                return null;
            }

            var oParam = {};
            for (var i = 0; i < aParams.length; i++) {
                var param = aParams[i].split('=');
                if (!!param[0]) {
                    if (!isNaN(+param[1])) {
                        param[1] = +param[1];
                    }
                    if (param[0].search(/\[(.*)\]/) >= 0) {

                        param[0].replace(/(.*)\[(.*)\]/, function (matched, $1, $2) {
                            if (util.isUndefined(oParam[$1])) {
                                if (!!$2) {
                                    oParam[$1] = {};
                                } else {
                                    oParam[$1] = [];
                                }
                            }

                            if (!!$2) {
                                oParam[$1][$2] = param[1];
                            } else {
                                oParam[$1].push(param[1]);
                            }
                        });
                    } else {
                        oParam[param[0]] = param[1];
                    }
                }
            }
            return oParam;
        },
        /**
         * 链接跳转
         * @param   {String} url 存在指定连接时跳转
         * @returns {boolean}  [[Description]]
         */
        go: function (url) {
            //valide
            if (!this.isString(url)) return false;

            location.assign(url);
        },
        /**
         * 历史记录回滚
         */
        back: function () {
            history.back();
        }
    };

    //若存在模块加载器，则定义为一个模块
    if (typeof window.define === 'function' && (window.define.amd || window.define.cmd)) {
        window.define([], util);
    }

    //注入到window对象
    if (typeof window.util === 'undefined') {
        window.util = util;
    } else {
        throw "存在同名的util库";
    }
}(this));